// resourceMgr.cpp
//
// Copyright (c) 2003 Philip Romanik, Amy Muntz
//
// General purpose string resource manager

#include "resourceMgr.h"
#include "xmlTools.h"
#include "debugStream.h"

namespace simple{
    
    //
    // StringResource
    //
    std::string  StringResource::sNullString;
    std::wstring StringResource::sNullWString;

    StringResource::StringResource (const std::wstring& str, 
                                    const std::wstring& notes, 
                                                                              unsigned int id) : _id (0)
    {
        _id = ResourceMgr::gOnly().addString (str, notes, id);
    }

    std::wstring StringResource::string () const
    {
        return ResourceMgr::gOnly().fetchString (_id);
    }



    //
    // ResourceMgr
    //
    ResourceMgr* ResourceMgr::_sOnly = 0;

    ResourceMgr& ResourceMgr::gOnly ()
    {
        if (!_sOnly) _sOnly = new ResourceMgr ();
        return *_sOnly;
    }

    ResourceMgr::ResourceMgr () : _id (0)
    {}

    void ResourceMgr::reset ()
    {
        _id = 0;
        _strings.clear ();
    }


    unsigned int ResourceMgr::hash (const std::wstring& str)
    {
        // The hash must respect the endian-ness of the system. The
        // computation will be based on little-endian.
        const wchar_t* p = str.c_str();
        unsigned int id = 0;
        unsigned int i;

        if (WideTools::gOnly().endian () == WideTools::eBigEndian) {
            // Flip each character to make it look little Endian
            for (i=0; i<str.size(); i++)
                id = id * 797 + WideTools::gOnly().swap (*p++);
        }
        else {
            for (i=0; i<str.size(); i++)
                id = id * 797 + (*p++);    // 797 is a nice prime number
        }

        return id;
    }



    unsigned int ResourceMgr::addString (const std::wstring& str, 
                                         const std::wstring& notes, 
                                         unsigned int id, bool overlay)
    {
        if (id == 0) {
            id = hash (str);    // Get a hash value, look at each byte
        }

        if (id == 0)
            return id;    // null string. These can be safely ignored.

        // Ignore the string if overlay is false and the string already exists
        if (!overlay) {
            stringmap::iterator i = _strings.find (id);
            if (i != _strings.end()) {
                cdebug << "ResourceMgr::addString. duplicate string" << str.c_str() << std::endl;
                return id;
            }
        }

        StringData data;
        data.str     = str;
        data.notes   = notes;
        _strings[id] = data;

        return id;
    }


    std::wstring ResourceMgr::fetchString (unsigned int id)
    {
        stringmap::iterator i = _strings.find (id);
        if (i == _strings.end())
            return L"";

        return (*i).second.str;
    }


    bool ResourceMgr::exportStrings (const std::string& file)
    {
        XMLTools xml;

        StringData data;
        stringmap::iterator i;

        for (i=_strings.begin(); i != _strings.end(); i++) {
            data = i->second;
            xml.writeResource ((*i).first, data.str, data.notes);
        }
        // We don't write empty files
        if (xml.xml().empty())
            return false;

        xml.writeTrailing ();

        // NOTE:
        // At least on MSVC, std::wofstream had trouble writing a binary
        // string (the BOM). Write as a byte stream instead, using
        // appropriate casting.
        std::ofstream output (file.c_str(), std::ios_base::binary);
        const std::wstring& str = xml.xml();
        output.write (reinterpret_cast<const char*>(str.c_str()), 
                      str.size()*sizeof(wchar_t));

        return true;
    }

    bool ResourceMgr::importStrings (const std::string& file)
    {
        // NOTE:
        // At least on MSVC, std::wifstream had trouble reading a binary
        // string (the BOM). Read as a byte stream in a single operation.
        // We use C-style I/O operations so fseek/ftell will tell us the
        // size of the file. fread() reads the data into a wide string.

        // Get size of file
        FILE* fp = fopen (file.c_str(), "rb");
        if (fp == 0)
            return false;

        fseek (fp, 0L, SEEK_END);
        long size = ftell (fp) / 2; // Number of wide characters
        rewind (fp);

        // Read the data into a properly sized wide string
        std::wstring data;
        data.resize (size-1);
        wchar_t* p = const_cast<wchar_t*>(data.c_str());

        wchar_t bom;
        fread (&bom, sizeof(wchar_t), 1, fp);
        fread (p, sizeof(wchar_t), size, fp);
        fclose (fp);

        // This will call itself recursively until it is done
        parseElement (data, WideTools::gOnly().checkBOM (bom));

        return true;
    }

    void ResourceMgr::parseElement (const std::wstring& data,
                                    WideTools::eBOM endian)
    {
        // Local, static copies of id, str, and notes are stored here and filled in when
        // the tags are received.
        static unsigned long id;
        static std::wstring  str;
        static std::wstring  notes;

        XMLTools xml (data, endian);
        while (true) {
            XMLElement element = xml.parse ();
            if (element.name.empty()) return;

            if (element.name == L"<resources>") {
                parseElement (element.value, endian);
            }
            else if (element.name == L"<phrase>") {
                parseElement (element.value, endian);
                // New values of id, str, notes should be available.
                addString (str, notes, id, true);
            }
            else if (element.name == L"<id>") {
                id = wcstol (element.value.c_str(), 0, 10);
            }
            else if (element.name == L"<string>") {
                str = element.value;
            }
            else if (element.name == L"<notes>") {
                notes = element.value;
            }
        }
    }
}